jsobject *cocoaFileObject;
jsobject *cocoaDirObject;

static jsvalue fileReadTextFile(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc);
static jsvalue writeTextFile(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc);
static jsvalue appendTextFile(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc);
static jsvalue fileDelete(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc);
static jsvalue fileDirectory(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc);
static jsvalue fileCreateDirectory(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc);
static jsvalue fileMove(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc);
static jsvalue fileCopy(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc);
static jsvalue fileExists(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc);
jsvalue readImageFile(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc);
static jsvalue dirList(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc);

static jsvalue findSpecialFolder(jscontext *cx, NSSearchPathDirectory fldr, NSSearchPathDomainMask domain) {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(fldr, domain, YES );
	
    if ( [paths count] > 0) {
		return jsstrFromNSString(cx, [paths objectAtIndex:0]);
    } else {
		return JSVAL_NULL;
	}
}

static jsvalue dirApplicationSupport(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	return findSpecialFolder(cx, NSApplicationSupportDirectory, NSUserDomainMask);
}
static jsvalue dirDownloads(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	return findSpecialFolder(cx, NSDownloadsDirectory, NSUserDomainMask);
}

static jsvalue dirApplications(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	return findSpecialFolder(cx, NSApplicationDirectory, NSSystemDomainMask);
}

static jsvalue dirDeveloper(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	return findSpecialFolder(cx, NSDeveloperDirectory, NSLocalDomainMask);
}

static jsvalue dirLibrary(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	return findSpecialFolder(cx, NSLibraryDirectory, NSLocalDomainMask);
}

static jsvalue dirUsers(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	return findSpecialFolder(cx, NSUserDirectory, NSLocalDomainMask);
}

static jsvalue dirHome(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	return jsstrFromNSString(cx, NSHomeDirectory());
}

static jsvalue dirDocuments(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	return findSpecialFolder(cx, NSDocumentDirectory, NSUserDomainMask);
}

static jsvalue dirDesktop(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	return findSpecialFolder(cx, NSDesktopDirectory, NSUserDomainMask);
}

static jsvalue fileCurrentDirectory(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	return jsstrFromNSString(cx, [[NSFileManager defaultManager] currentDirectoryPath]);
}

static jsvalue fileReadTextFile(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	if (argc < 1)
		return JSVAL_FALSE;
	NSString *path = jsasstrRetain(cx, args[0]);
	if (!path) {
		return JSVAL_VOID;
	}
		
	NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
	[path release];
	
	if (!content) {
		return JSVAL_VOID;
	}
	return jsstrFromNSString(cx, content);
}

static jsvalue writeTextFile(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	if (argc < 2)
		return JSVAL_VOID;
	NSString *path = jsasstrRetain(cx, args[0]);
	if (!path) {
		return JSVAL_FALSE;
	}

	NSString *content = jsasstrRetain(cx, args[1]);
	
	[content writeToFile:path atomically:NO encoding:NSUTF8StringEncoding error:nil];
	[path release];
	[content release];
	return JSVAL_TRUE;
}

static jsvalue appendTextFile(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	if (argc < 2)
		return JSVAL_VOID;
	NSString *path = jsasstrRetain(cx, args[0]);
	if (!path) {
		return JSVAL_FALSE;
	}
	
	NSString *content = jsasstrRetain(cx, args[1]);
	
	NSFileHandle *handle;
	jsbool success = 0;
	if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
		handle = [NSFileHandle fileHandleForUpdatingAtPath:path];
		if (handle != nil) {
			[handle seekToEndOfFile];
			[handle writeData:[content dataUsingEncoding:NSUTF8StringEncoding]];
			[handle closeFile];
			success = YES;
		}
	}
	else {
		success = [content writeToFile:path atomically:NO encoding:NSUTF8StringEncoding error:nil];
	}

	[path release];
	[content release];
	return BOOLEAN_TO_JSVAL(success);
}

static jsvalue fileDelete(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	if (argc < 1)
		return JSVAL_VOID;
	NSString *path = jsasstrRetain(cx, args[0]);
	if (!path) {
		return JSVAL_VOID;
	}
	
	jsbool success = NO;
	if ([[NSFileManager defaultManager] removeFileAtPath:path handler:nil])
		success = 1;
	[path release];
	return BOOLEAN_TO_JSVAL(success);
}

static jsvalue fileExists(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	if (argc < 1)
		return JSVAL_VOID;
	NSString *path = jsasstrRetain(cx, args[0]);
	if (!path) {
		return JSVAL_VOID;
	}
	
	jsbool success = [[NSFileManager defaultManager] fileExistsAtPath:path];
	[path release];
	return BOOLEAN_TO_JSVAL(success);
}

static jsvalue fileCreateDirectory(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	if (argc < 1)
		return JSVAL_VOID;
	NSString *path = jsasstrRetain(cx, args[0]);
	if (!path) {
		return JSVAL_VOID;
	}
	
	jsbool success = [[NSFileManager defaultManager] createDirectoryAtPath:path attributes:nil];
	[path release];
	return BOOLEAN_TO_JSVAL(success);
}

static jsvalue fileDirectory(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	if (argc < 1)
		return JSVAL_VOID;
	NSString *path = jsasstrRetain(cx, args[0]);
	if (!path) {
		return JSVAL_VOID;
	}
	
	NSArray *arr = [[NSFileManager defaultManager] directoryContentsAtPath:path];
	[path release];
	
	if (!arr) {
		return JSVAL_NULL;
	}

	jsvalue result = jsnewarray(cx);
	jsvalue el = 0;
	jsaddroot(cx, &result);
	jsaddroot(cx, &el);

	int count = [arr count];
	for (int idx = 0; idx < count; idx++) {
		NSString *name = [arr objectAtIndex:idx];
		el = jsstrFromNSString(cx, name);
		jssetelem(cx, result, idx, el);
	}
	
	jsremoveroot(cx, &result);
	jsremoveroot(cx, &el);
	return OBJECT_TO_JSVAL(result);
}

static jsvalue fileattrToObject(jscontext *cx, NSDictionary *attr) {
	NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init];
	
	jsvalue item = jsnewobject(cx);
	jsvalue el;
	
	jsaddroot(cx, &item);
	
	NSString *fileType = [attr fileType];
	
	if ([fileType isEqualToString: NSFileTypeDirectory]) {
		el = jsstrFromNSString(cx, @"dir");
	} else if ([fileType isEqualToString: NSFileTypeRegular]) {
		el = jsstrFromNSString(cx, @"file");
	} else if ([fileType isEqualToString: NSFileTypeSymbolicLink]) {
		el = jsstrFromNSString(cx, @"symbolic");
	} else if ([fileType isEqualToString: NSFileTypeBlockSpecial]) {
		el = jsstrFromNSString(cx, @"block");
	} else if ([fileType isEqualToString: NSFileTypeCharacterSpecial]) {
		el = jsstrFromNSString(cx, @"character");
	} else {
		el = JSVAL_NULL;
	}
	
	jsset(cx, item, 0, "type", el);
		
	jsset(cx, item, 0, "size", jsnumi(cx, [attr fileSize]));
	
	NSDate *dt = [attr fileModificationDate];
	jsvalue secsSince1970 = jsnum(cx, [dt timeIntervalSince1970] * 1000);
	jsaddroot(cx, &secsSince1970);
	jsset(cx, item, 0, "modified", jsnew(cx, cx->slots[7], &secsSince1970, 1));
	jsremoveroot(cx, &secsSince1970);
	/*
	 NSDate *dt = [attr fileModificationDate];
	 if (dt != nil) {
	 jsvalue timev;
	 JS_NewNumberValue(cx, , &timev);
	 JS_CallFunctionName(cx, global, "__constructDate", 1, &timev, &el);
	 JS_SetProperty(cx, item, "modified", &el);
	 }
	 
	 dt = [attr fileCreationDate];
	 if (dt != nil) {
	 jsval timev;
	 JS_NewNumberValue(cx, [dt timeIntervalSince1970] * 1000, &timev);
	 JS_CallFunctionName(cx, global, "__constructDate", 1, &timev, &el);
	 JS_SetProperty(cx, item, "created", &el);
	 }
	 */
	jsset(cx, item, 0, "extensionHidden", BOOLEAN_TO_JSVAL([attr fileExtensionHidden]));
	jsset(cx, item, 0, "immutable", BOOLEAN_TO_JSVAL([attr fileIsImmutable]));
	jsset(cx, item, 0, "posixPermissions", INT_TO_JSVAL([attr filePosixPermissions]));
	jsset(cx, item, 0, "owner", jsstrFromNSString(cx, [attr fileOwnerAccountName]));
	
	[innerPool release];
	
	jsremoveroot(cx, &item);

	return item;
}

static jsvalue fileInfo(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	if (argc < 1)
		return JSVAL_VOID;
	NSString *path = jsasstrRetain(cx, args[0]);
	if (!path) {
		return JSVAL_VOID;
	}

	NSDictionary *attr = [[NSFileManager defaultManager] fileAttributesAtPath:path traverseLink:NO];
	[path release];
	
	if (!attr)
		return JSVAL_VOID;
	
	return fileattrToObject(cx, attr);
}

static jsvalue dirList(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	jsbool recursive = NO;
	
	if (argc < 1)
		return JSVAL_VOID;
	NSString *path = jsasstrRetain(cx, args[0]);
	if (!path) {
		return JSVAL_VOID;
	}
	
	if (argc > 1) {
		recursive = jsasbool(cx, args[1]);
	}
	
	NSDirectoryEnumerator *en = [[NSFileManager defaultManager] enumeratorAtPath:path];
	[path release];
	if (!en) {
		return JSVAL_NULL;
	}
	
	jsvalue result = jsnewarray(cx);
	jsvalue el = JSVAL_VOID;
	jsaddroot(cx, &result);
	jsaddroot(cx, &el);
	
	NSString *file;

	int idx = 0;
	while (file = [en nextObject]) {
		NSDictionary *attr = [en fileAttributes];

		jsvalue item = fileattrToObject(cx, attr);
		
		jssetelem(cx, result, INT_TO_JSVAL(idx), item);

		el = jsstrFromNSString(cx, file);

		jsset(cx, item, 0, "name", el);
		
		NSString *fileType = [attr fileType];

		if (!recursive && [fileType isEqualToString: NSFileTypeDirectory]) {
			[en skipDescendents];
		}
		
		idx++;
//		JS_MaybeGC(cx);
	}
	
	jsremoveroot(cx, &result);
	jsremoveroot(cx, &el);
	return OBJECT_TO_JSVAL(result);
}

static jsvalue fileMove(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	if (argc < 2)
		return JSVAL_FALSE;
	NSString *path = jsasstrRetain(cx, args[0]);
	if (!path)
		return JSVAL_FALSE;
	NSString *dpath = jsasstrRetain(cx, args[1]);
	if (!dpath) {
		[path release];
		return JSVAL_FALSE;
	}
	
	jsvalue rv;
	if ([[NSFileManager defaultManager] movePath:path toPath:dpath handler:nil])
		rv = BOOLEAN_TO_JSVAL(1);
	else
		rv = BOOLEAN_TO_JSVAL(0);
	return rv;
}

static jsvalue fileCopy(jscontext *cx, jsframe *outer, jsvalue obj, jsvalue *args, int argc) {
	if (argc < 2)
		return JSVAL_FALSE;
	NSString *path = jsasstrRetain(cx, args[0]);
	if (!path)
		return JSVAL_FALSE;
	NSString *dpath = jsasstrRetain(cx, args[1]);
	if (!dpath) {
		[path release];
		return JSVAL_FALSE;
	}
	
	jsvalue rv;
	if ([[NSFileManager defaultManager] copyPath:path toPath:dpath handler:nil])
		rv = BOOLEAN_TO_JSVAL(1);
	else
		rv = BOOLEAN_TO_JSVAL(0);

	[path release];
	[dpath release];
	
	return rv;
}

void registerFile(jscontext *cx) {
	jsvalue File, Dir;
	cx->slots[14] = File = jsnewobject(cx);
	jsset(cx, File, 0, "read", bindFunction(cx, fileReadTextFile));
	jsset(cx, File, 0, "write", bindFunction(cx, writeTextFile));
	jsset(cx, File, 0, "append", bindFunction(cx, appendTextFile));
	jsset(cx, File, 0, "deleteFile", bindFunction(cx, fileDelete));
	jsset(cx, File, 0, "remove", bindFunction(cx, fileDelete));
	jsset(cx, File, 0, "move", bindFunction(cx, fileMove));
	jsset(cx, File, 0, "info", bindFunction(cx, fileInfo));
	jsset(cx, File, 0, "copy", bindFunction(cx, fileCopy));
	jsset(cx, File, 0, "exists", bindFunction(cx, fileExists));
	jsset(cx, File, 0, "readImage", bindFunction(cx, readImageFile));
	jssetdynamic(cx, File, 0, "current", fileCurrentDirectory, nil);
	
	cx->slots[18] = Dir = jsnewobject(cx);
	jsset(cx, Dir, 0, "names", bindFunction(cx, fileDirectory));
	jsset(cx, Dir, 0, "list", bindFunction(cx, dirList));
	jsset(cx, Dir, 0, "create", bindFunction(cx, fileCreateDirectory));
	jsset(cx, Dir, 0, "remove", bindFunction(cx, fileDelete));
	jssetdynamic(cx, Dir, 0, "applicationSupport", dirApplicationSupport, nil);
	jssetdynamic(cx, Dir, 0, "downloads", dirApplicationSupport, nil);
	jssetdynamic(cx, Dir, 0, "applications", dirApplications, nil);
	jssetdynamic(cx, Dir, 0, "developer", dirDeveloper, nil);
	jssetdynamic(cx, Dir, 0, "library", dirLibrary, nil);
	jssetdynamic(cx, Dir, 0, "users", dirUsers, nil);
	jssetdynamic(cx, Dir, 0, "documents", dirDocuments, nil);
	jssetdynamic(cx, Dir, 0, "desktop", dirDesktop, nil);
	jssetdynamic(cx, Dir, 0, "home", dirHome, nil);
}

